/*
 * @Description: 
 * @Author: YangHeng
 * @Date: 2021-08-12 11:06:55
 * @FilePath: \web_server_app\websocket\index.js
 */

const WebSocketServer = require('ws').Server;
const mockHttp = require('../utils/mock-http');
const { isString } = require('core-util-is');
const { omit } = require('lodash');
const { Worker } = require('worker_threads')
var PubSub = require("pubsub-js");


const defaultOptions = {
  path: '/ws'
}

const WS_STATUS = {
  CONNECTING: 0,
  OPEN: 1,
  CLOSING: 2,
  CLOSED: 3
};

module.exports = class Wss {
  constructor(server, config) {
    this.config = Object.assign(
      {}, defaultOptions,
      omit(config || {}, ['host', 'port', 'backlog', 'server', 'verifyClient', 'handleProtocols', 'noServer'])
    )
    if (!config.child) {
      // 合并配置文件
      this.server = server
      this.config.server = server

      this.wss = new WebSocketServer(this.config)
      this.wss.webClients = {}
      this.wss.on('error', civi.logs.error)
      this.wss.on('connection', (ws) => {
        // 客户端链接，发送open事件
        ws.send(JSON.stringify({ event: 'open', data: 'success' }))

        ws.on('message', (message) => {
          let data
          try {
            data = JSON.parse(message.toString())
          } catch (err) { civi.logs.error('Expect to receive a JSON string') }
          if (!data) return

          // 解析数据
          if (!data.event) return civi.logs.error('The received data format does not match. Please refer to `{"event": "event", "data": "data"}`')

          // 前端数据链接
          if (data.event === 'clientOpen') {
            let uuid = data.data
            if (this.wss.webClients[uuid]) {
              this.wss.webClients[uuid].close()
              delete this.wss.webClients[uuid]
            }
            ws.uuid = uuid;
            this.wss.webClients[uuid] = ws
          } else if (data.event === 'cameraConnection') {
            let camera = data.data
            ws.camera = camera
          }

          // 匹配控制器
          if (this.config.messages && this.config.messages[data.event] && isString(this.config.messages[data.event])) {
            let url = this.config.messages[data.event]
            if (url[0] !== '/') url = `/${url}`;
            // mockHttp({ url, wsData: data.data, websocket: ws, method: 'WSS' }, civi.app)
            civi.action(url.substr(1), { body: data.data, params: { ws: ws } })
          }
        })

        ws.on('close', () => {
          ws.close();
          // 关闭
          if (ws.uuid && this.wss.webClients[ws.uuid]) {
            this.wss.webClients[ws.uuid].close()
            delete this.wss.webClients[ws.uuid]
          }
        })
      })
    } else {
      this.child = true;
      this.worker = new Worker(__dirname + '/child.js', { workerData: { config } })
      // 监听子进程消息处理
      this.worker.on('message', (data) => {
        if (data.event) {
          if (data.event == 'create_ok') {
            civi.logs.success('Create a WebSocket service', config.port)
          } else if (data.event == 'yuvPlayerOpen') {
            let rtsp = data.data
            civi.cam2yuv.decode(rtsp)
          } else if (data.event == 'yuvPlayerClose') {
            let rtsp = data.data
            civi.cam2yuv.discontact(rtsp)
          }
          else {
            // 匹配控制器
            if (this.config.messages && this.config.messages[data.event] && isString(this.config.messages[data.event])) {
              let url = this.config.messages[data.event]
              if (url[0] !== '/') url = `/${url}`;
              // mockHttp({ url, wsData: data.data, method: 'WSS' }, civi.app)
              civi.action(url.substr(1), { body: data.data, params: { ws: { send: () => { } } } })
            }
          }
        }
      })
      this.worker.postMessage({ event: 'create', data: config })
    }
  }
  broadcastDisplay(rtsp, data) {
    this.worker.postMessage({ event: 'broadcastDisplay', data: { rtsp, data } })
  }
  /**
   * @description: broadcast event
   * @param {*} event 事件名
   * @param {*} data 数据
   * @param {*} wait 是否等待 如果为false的话 当还有数据未发送完成则丢弃当前数据包
   */
  broadcast(event, data, wait = true) {
    if (data.toJSON) data = data.toJSON()
    if (!this.child) {
      let buf = JSON.stringify({ event, data })
      Object.keys(this.wss.webClients).forEach((uuid) => {
        let client = this.wss.webClients[uuid]
        // wait 等于false 并且客户端数据未接收完成 返回
        if (!wait && client.bufferedAmount > 0) {
          return;
        }
        if (client.readyState === WS_STATUS.OPEN) {
          client.send(buf)
        }
      })
    } else {
      // 通知子进程
      this.worker.postMessage({ event: 'broadcast', data: { event, data, wait } })
    }
  }
  /**
   * @description: broadcast event
   * @param {*} data 数据
   */
  broadcastPull(data) {
    if (!this.child) {
      Object.keys(this.wss.webClients).forEach((uuid) => {
        let client = this.wss.webClients[uuid]
        if (client.bufferedAmount > 0) return;
        if (client.readyState === WS_STATUS.OPEN && client.camera) {
          try {
            let text = JSON.stringify({
              event: 'pullVideo',
              data: {
                [client.camera.type]: {
                  [client.camera.rtsp]: data[client.camera.type][client.camera.rtsp]
                }
              }
            })
            client.send(Buffer.from(text, 'utf-8'))
          } catch (e) {
          }
        }
      })
    } else {
      this.worker.postMessage({ event: 'broadcastPull', data: data })
    }
  }
  /**
   * 广播YUV数据
   * @param {*} data 
   */
  broadcastYUV(data) {
    if (this.child) {
      this.worker.postMessage({ event: 'broadcastYUV', data: data })
    }
  }
  /**
   *  @description: close ws
   * @param {*} client 
   * @param {*} event 
   * @param {*} data 
   */
  sendSync(client, data) {
    new Promise((resolve, reject) => {
      if (client.bufferedAmount > 0) reject('error')
      else {
        if (client.readyState !== WS_STATUS.OPEN) reject('error')
        else {
          client.send(data)
          // 循环检测是否发送完成
          let timer = setInterval(() => {
            if (client.bufferedAmount <= 0) {
              clearInterval(timer)
              resolve('success')
            }
          })
        }
      }
    })
  }
}

/*
async function sendWssData() {
  let data = JSON.stringify({ event: 'pullVideo', data: civi.pullWsData })
  for (let uuid of Object.keys(civi.wss.webClients)) {
    let client = civi.wss.webClients[uuid]
    await civi.wss.sendSync(client, data)
  }
  setTimeout(sendWssData, 5)
}
*/